package com.jse;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandler;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Base64;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;

import com.jse.json.Json;

@SuppressWarnings("unchecked")
public class Http {

	private final static Log log = Log.get("Http");
	/**
     * 枚举 HTTP 状态码及其描述信息。
     */
    public enum Status {
        CONTINUE(100, "Continue"),
        OK(200, "OK"),
        CREATED(201, "Created"),
        ACCEPTED(202, "Accepted"),
        NOT_AUTHORITATIVE(203, "Non-Authoritative Information"),
        NO_CONTENT(204, "No Content"),
        RESET(205, "Reset Content"),
        PARTIAL(206, "Partial Content"),
        MULTIPLE_CHOICES(300, "Multiple Choices"),
        MOVED_PERMANENTLY(301, "Moved Permanently"),
        MOVED_TEMPORARILY(302, "Temporary Redirect"),
        SEE_OTHER(303, "See Other"),
        NOT_MODIFIED(304, "Not Modified"),
        USE_PROXY(305, "Use Proxy"),
        BAD_REQUEST(400, "Bad Request"),
        UNAUTHORIZED(401, "Unauthorized"),
        PAYMENT_REQUIRED(402, "Payment Required"),
        FORBIDDEN(403, "Forbidden"),
        NOT_FOUND(404, "Not Found"),
        METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
        NOT_ACCEPTABLE(406, "Not Acceptable"),
        PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"),
        REQUEST_TIMEOUT(408, "Request Time-Out"),
        CONFLICT(409, "Conflict"),
        GONE(410, "Gone"),
        LENGTH_REQUIRED(411, "Length Required"),
        PRECONDITION_FAILED(412, "Precondition Failed"),
        REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"),
        REQUEST_URI_TOO_LONG(414, "Request-URI Too Large"),
        UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
        INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
        NOT_IMPLEMENTED(501, "Not Implemented"),
        BAD_GATEWAY(502, "Bad Gateway"),
        SERVICE_UNAVAILABLE(503, "Service Unavailable"),
        GATEWAY_TIMEOUT(504, "Gateway Timeout"),
        HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version Not Supported");

        private final int code;
        private final String message;

        Status(int code, String message) {
            this.code = code;
            this.message = message;
        }

        public int getCode() {
            return code;
        }

        public String getMessage() {
            return message;
        }
    }

    private static final Map<Integer, Status> statusCodes = new HashMap<Integer, Status>() {{
        for (Status s : Status.values()) {
            put(s.getCode(), s);
        }
    }};

	public static final String CRLF = "\r\n";
	public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.76";

	public static Object send(String method, String url, Object body) {
		return send(null, method, url, body, 0, null, false, null);
	}

	public static Object send(String method, String url, Object body, Map<String, String> headers) {
		return send(null, method, url, body, 0, headers, false, null);
	}

	public static Object send(String method, String url, Object body, Map<String, String> headers, Object type) {
		return send(null, method, url, body, 0, headers, false, type);
	}

	public static Object send(String method, String url, Object body, Map<String, String> headers, boolean async,
			Object type) {
		return send(null, method, url, body, 0, headers, async, type);
	}

	public static Object send(Version verion, String method, String url, Object body, int timeout,
			Map<String, String> headers, boolean async, Object type) {
		try {
			HttpRequest.Builder build = HttpRequest.newBuilder(URI.create(url));
			if (timeout != 0)
				build.timeout(Duration.ofMillis(timeout));
			if (verion == null)
				verion = Version.HTTP_1_1;
			build.version(verion);
			if (headers == null)
				headers = Map.of();
			headers.forEach((k, v) -> {
				build.setHeader(k.toString(), v.toString());
			});
			if (!headers.containsKey("User-Agent")) {
				build.setHeader("User-Agent", USER_AGENT);
			}
			if (!headers.containsKey("referer"))
				build.setHeader("referer", "http://jse.jdk17.cn");
			BodyPublisher bp = null;
			if (body == null) {
				bp = BodyPublishers.noBody();
			} else if (body instanceof File f) {
				bp = BodyPublishers.ofFile(f.toPath());
			} else if (body instanceof Path p) {
				bp = BodyPublishers.ofFile(p);
			} else if (body instanceof byte[] bs) {
				bp = BodyPublishers.ofByteArray(bs);
			} else if (body instanceof InputStream in) {
				bp = BodyPublishers.ofInputStream(() -> in);
			} else if (body instanceof Collection<?> c) {// body 为list则只支持json与xml
				var j = Json.jsonArray(c);
				String contentType = headers.get("Content-Type").toString();
				if (contentType.startsWith("application/xml") || contentType.startsWith("text/xml")) {
					bp = BodyPublishers.ofString(j.toXml());
				} else {
					bp = BodyPublishers.ofString(j.toString());// 普通文本处理
				}
			} else if (body instanceof Map<?, ?> map) {
				String content_Type = headers.get("Content-Type").toString();
				if (content_Type.startsWith("application/json")) {
					bp = BodyPublishers.ofString(Json.toJson(map));
				} else if (content_Type.startsWith("application/xml") || content_Type.startsWith("text/xml")) {
					bp = BodyPublishers.ofString(Json.jsonObject(map).toXml());
				} else if (content_Type.startsWith("multipart/form-data; boundary=")) {
					String boundary = headers.get("Content-Type").toString().substring(30).trim();
					Map<String, String> textMap = new HashMap<>();// 普通表单参数
					Map<String, File> fileMap = new HashMap<>();
					for (Object k : map.keySet()) {
						Object v = map.get(k);
						if (v == null) {
							textMap.put(k.toString(), "");
						} else if (v instanceof File f) {
							fileMap.put(k.toString(), f);
						} else {
							textMap.put(k.toString(), v.toString());
						}
					}
					StringBuilder sb = new StringBuilder();
					ByteArrayOutputStream out = new ByteArrayOutputStream();
					textMap.forEach((k, v) -> {// 写普通表单参数
						sb.append("--").append(boundary).append(CRLF)// 写分隔符--${boundary}，并回车换行
								// 写描述信息：Content-Disposition: form-data; name="参数名"，并两个回车换行
								.append(String.format("Content-Disposition: form-data; name=\"%s\"", k)).append(CRLF)
								.append(CRLF).append(v).append(CRLF);// 写具体内容：参数值，并回车换行
					});
					out.write(sb.toString().getBytes());
					sb.setLength(0);
					var base64 = headers.get("base64");
					fileMap.forEach((k, v) -> {// 写文件参数
						try (InputStream in = new FileInputStream(v)) {
							sb.append("--").append(boundary).append(CRLF);
							String fileName = v.getName();
							String contentDispositionStr = String.format(
									"Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"", k, fileName) + CRLF;
							sb.append(contentDispositionStr);
							String suffix = Fs.suffix(fileName);
							String mimeType = Fs.mimeType(fileName, "application/octet-stream");
							String contentType = "Content-Type: " + mimeType + CRLF + CRLF;
							sb.append(contentType);
							out.write(sb.toString().getBytes());
							sb.setLength(0);
							if (base64 != null// jpg,png,jpeg,psd,gif,bmp,webp
									&& suffix.equals(base64)) {
								String img = "data:" + mimeType + ";base64," + Awts.base64(in, suffix);
								out.write(img.getBytes());
							} else {
								out.write(in.readAllBytes());
							}
							out.write(CRLF.getBytes());// 回车换行
						} catch (Exception e) {
							e.printStackTrace();
						}
					});
					out.write((CRLF + "--" + boundary + "--" + CRLF).getBytes());// 回车换行
					bp = BodyPublishers.ofInputStream(() -> {// 转换formData
						return new ByteArrayInputStream(out.toByteArray());
					});
				} else {
					StringBuilder sb = new StringBuilder();// 普通类型
					map.forEach((k, v) -> {
						sb.append("&").append(k).append("=").append(v);
					});
					bp = BodyPublishers.ofString(sb.substring(1));
				}
			} else {
				String s = body.toString();
				if (s.length() > 0 && s.length() < 255 && (s.charAt(0) != '{' || s.charAt(0) != '[')
						&& (s.charAt(0) == '/' || s.charAt(1) == ':') && Fs.exists(s)) {
					bp = BodyPublishers.ofFile(Path.of(s));
				} else {
					bp = BodyPublishers.ofString(body.toString());
				}
			}
			build.method(method, bp);
			BodyHandler<?> bh = BodyHandlers.ofString();
			if (type == null) {
				bh = BodyHandlers.ofString(StandardCharsets.UTF_8);
			} else if (type instanceof String s) {
				bh = BodyHandlers.ofByteArray();
				if (s.equals("byte")) {
					bh = BodyHandlers.ofByteArray();
				} else if (s.equals("in")) {
					bh = BodyHandlers.ofInputStream();
				} else if (s.equals("stream")) {
					bh = BodyHandlers.ofLines();
				} else if (s.equals("pub")) {
					bh = BodyHandlers.ofPublisher();
				} else {
					bh = BodyHandlers.ofString(StandardCharsets.UTF_8);
				}
			}else if(type instanceof BodyHandler b) {
				bh=b;
			}
			else if (type instanceof Path p) {
				if (Files.isDirectory(p)) {
					bh = BodyHandlers.ofFileDownload(p);
				} else {
					bh = BodyHandlers.ofFile(p);
				}
			} else if (type instanceof File f) {
				if (f.isDirectory()) {
					bh = BodyHandlers.ofFileDownload(f.toPath());
				} else {
					bh = BodyHandlers.ofFile(f.toPath());
				}
			} else if (type instanceof Charset charset) {
				bh = BodyHandlers.ofString(charset);
			} else if (type instanceof Consumer c) {// Consumer<Optional<byte[]>> consumer
				bh = BodyHandlers.ofByteArrayConsumer(c);
			}
			if (async) {
				return HttpClient.newHttpClient().sendAsync(build.build(), bh);
			} else {
				return HttpClient.newHttpClient().send(build.build(), bh);
			}
		} catch (IOException | InterruptedException e) {
			log.warn("%s %s err:%s", method, url, e.getMessage());
		}
		return null;
	}

	public static String get(String url) {
		return ((HttpResponse<String>) send("GET", url, null)).body();
	}
//	public static String get(String url) {
//		HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build();
//		try {
//			return HttpClient.newBuilder().build().send(request,BodyHandlers.ofString()).body();
//		} catch (IOException | InterruptedException e) {
//			throw new RuntimeException(e);
//		}
//	}

	public static String get(String url, Map<?, ?> param) {
		return ((HttpResponse<String>) send("GET", url, param)).body();
	}

	public static String post(String url, Map<?, ?> param) {
		return ((HttpResponse<String>) send("POST", url, param)).body();
	}

	public static String postJson(String url, String json) {
		return ((HttpResponse<String>) send("POST", url, json, Map.of("Content-Type", "application/json"))).body();
	}

	public static String postJson(String url, Map<String,Object> json) {
		Map<String,String> header=new HashMap<String,String>();
		String type="application/json";
		if(json.containsKey("#Content-Type"))type=json.remove("#Content-Type").toString();
		header.put("Content-Type", type);
		Iterator<Map.Entry<String, Object>> it = json.entrySet().iterator();  
        while(it.hasNext()){  
            Entry<String, Object> entry = it.next();  
            String k=entry.getKey();
            if(k.charAt(0)=='#') {
            	Object v=entry.getValue();
            	header.put(k.substring(1),v.toString());
            	it.remove();
            }  
        }
		return ((HttpResponse<String>) send("POST", url, json,header)).body();
	}

	public static String postXml(String url, String xml) {
		return ((HttpResponse<String>) send("POST", url, xml, Map.of("Content-Type", "application/xml"))).body();
	}

	public static String postXml(String url, Map<?, ?> xml) {
		return ((HttpResponse<String>) send("POST", url, xml, Map.of("Content-Type", "application/xml"))).body();
	}

	public static void down(String url, String path) {
		down(url, path, true);
	}

	public static void down(String url, String path, boolean async) {
		send(Version.HTTP_1_1, "GET", url, null, 0, null, async, Path.of(path));
	}

	public static String basicAuth(String username, String password) {
		return "Basic " + Base64.getEncoder()
				.encodeToString(username.concat(":").concat(password).getBytes(StandardCharsets.UTF_8));
	}
	
	 /**
     * 获取给定 HTTP 状态码的描述信息。
     * 
     * @param code HTTP 状态码。
     * @return 与状态码关联的描述信息。
     */
    public static String msg(int code) {
        Status status = statusCodes.get(code);
        return status != null ? status.getMessage() : " ";
    }

	public static void main(String[] args) throws InterruptedException {
//		down("https://cn.bing.com/th?id=OJ.ilrwlLiKV5AQIA&w=80&h=80&c=8&rs=1&pid=academic","d:/1.png",true);
//		Thread.sleep(2000l);
		System.out.println(basicAuth("zs", "123456"));
	}
}
